home *** CD-ROM | disk | FTP | other *** search
/ SGI Developer Toolbox 6.1 / SGI Developer Toolbox 6.1 - Disc 4.iso / public / fax / src / util / textfmt.c++ < prev    next >
C/C++ Source or Header  |  1994-08-01  |  27KB  |  986 lines

  1. /*    $Header: /usr/people/sam/fax/util/RCS/textfmt.c++,v 1.26 1994/04/21 19:23:17 sam Rel $ */
  2. /*
  3.  * Copyright (c) 1993, 1994 Sam Leffler
  4.  * Copyright (c) 1993, 1994 Silicon Graphics, Inc.
  5.  *
  6.  * Permission to use, copy, modify, distribute, and sell this software and 
  7.  * its documentation for any purpose is hereby granted without fee, provided
  8.  * that (i) the above copyright notices and this permission notice appear in
  9.  * all copies of the software and related documentation, and (ii) the names of
  10.  * Sam Leffler and Silicon Graphics may not be used in any advertising or
  11.  * publicity relating to the software without the specific, prior written
  12.  * permission of Sam Leffler and Silicon Graphics.
  13.  * 
  14.  * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
  15.  * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
  16.  * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
  17.  * 
  18.  * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
  19.  * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
  20.  * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
  21.  * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
  22.  * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
  23.  * OF THIS SOFTWARE.
  24.  */
  25.  
  26. /*
  27.  * A sort of enscript clone.  This program is derived from the
  28.  * lptops program by Nelson Beebe.  Any bugs are my fault...
  29.  */
  30. #include <stdio.h>
  31. #include <ctype.h>
  32. #include <errno.h>
  33. #include <string.h>
  34. #include <unistd.h>
  35. #include <stdlib.h>
  36. #include <time.h>
  37. #include <stdarg.h>
  38. #include <sys/types.h>
  39. #include <sys/stat.h>
  40. #include <dirent.h>
  41.  
  42. #include "Types.h"
  43. #include "Array.h"
  44. #include "PageSize.h"
  45. extern "C" {
  46. #include <locale.h>
  47. }
  48.  
  49. typedef long Coord;        // local coordinates
  50. #define LUNIT     (72*20)        // local coord system is .05 scale
  51. #define    ICVT(x) ((Coord)((x)*LUNIT))    // scale inches to local coordinates
  52. #define    CVTI(x)    (float(x)/LUNIT)    // convert coords to inches
  53.  
  54. inline Coord fxmin(Coord a, Coord b)    { return (a < b) ? a : b; }
  55. inline Coord fxmax(Coord a, Coord b)    { return (a > b) ? a : b; }
  56.  
  57. #define COLFRAC        35    // 1/fraction of col width for margin
  58.  
  59. char*    tempfile;        // temp filename
  60. FILE*    tf;            // temporary output file
  61. #define NCHARS 256                // number of chars per font
  62. long    CharWidth[NCHARS];    // font width array
  63. char*    prog;            // program name
  64.  
  65. fxDECLARE_PrimArray(SizetArray, size_t);
  66. fxIMPLEMENT_PrimArray(SizetArray, size_t);
  67.  
  68. fxBool    gaudy        = FALSE;// emit gaudy headers
  69. fxBool    landscape    = FALSE;// horizontal landscape mode output
  70. fxBool    useISO8859    = TRUE;    // use the ISO 8859-1 character encoding
  71. fxBool    reverse        = FALSE;// page reversal flag
  72. fxBool    wrapLines    = TRUE;    // wrap/truncate lines
  73. fxBool    headers        = TRUE;    // emit page headers
  74. const char* font_name;        // text font name
  75. int    firstPageNum    = 1;    // starting page number
  76. int    numcol        = 1;    // number of text columns
  77. SizetArray* pageOff;        // page offset table
  78. float    physPageHeight;        // physical page height (inches)
  79. float    physPageWidth;        // physical page width (inches)
  80. Coord    pointSize    = -1;    // font point size in big points
  81. Coord    lm, rm;            // left, right margins in local coordinates
  82. Coord    tm, bm;            // top, bottom margin in local coordinates
  83. const char* curFile;        // current input file
  84. char    modDate[80];        // date of last modification to input file
  85. char    modTime[80];        // time of last modification to input file
  86. int    tabStop        = 8;    // n-column tab stop
  87.  
  88. Coord    lineHeight    = 0;    // inter-line spacing
  89. fxBool    boc;            // at beginning of a column
  90. fxBool    bop;            // at beginning of a page
  91. int    column        = 1;    // current text column # (1..numcol)
  92. Coord    col_margin    = 0L;    // inter-column margin
  93. Coord    col_width;        // column width in local coordinates
  94. int    level;            // PS string parenthesis level
  95. Coord    outline        = 0L;    // page and column outline linewidth
  96. Coord    pageHeight;        // page height in local coordinates
  97. int    pageNum        = 1;    // current page number
  98. Coord    pageWidth;        // page width in local coordinates
  99. Coord    right_x;        // column width (right hand side x)
  100. Coord    tabWidth;        // tab stop width in local units
  101. Coord    x, y;            // current coordinate
  102.  
  103. static void Copy_Block(long ,long);
  104. static void doFile(FILE *);
  105. static void endLine(void);
  106. static void endTextLine();
  107. static void endTextCol();
  108. static Coord inch(const char *);
  109. static void readMetrics(void);
  110. static fxBool findFont(const char* name);
  111. static void setDefaultFont();
  112. static void usage();
  113. static void fatal(const char* fmt...);
  114. static void emitPrologue(FILE*, int argc, char* argv[]);
  115. static void emitTrailer(FILE*);
  116. static void setupPageSize(const char* name);
  117. static void setupMargins(const char* s);
  118.  
  119. int
  120. main(int argc, char* argv[])
  121. {
  122.     extern int optind;
  123.     extern char* optarg;
  124.     int c;
  125.  
  126. #ifdef LC_CTYPE
  127.     setlocale(LC_CTYPE, "");
  128. #endif
  129.     setDefaultFont();
  130.     pageOff = new SizetArray;
  131.  
  132.     lm = rm = inch("0.25in");
  133.     tm = inch("0.85in");
  134.     bm = inch("0.5in");
  135.     tabStop = 8;
  136.     setupPageSize("default");
  137.  
  138.     prog = argv[0];
  139.     while ((c = getopt(argc, argv, "f:m:M:o:p:s:V:12BcDGrRU")) != -1)
  140.     switch(c) {
  141.     case '1':        // 1-column output
  142.     case '2':        // 2-column output
  143.         numcol = c - '0';
  144.         break;
  145.     case 'B':
  146.         headers = FALSE;
  147.         break;
  148.     case 'c':        // clip/cut instead of wrapping lines
  149.         wrapLines = FALSE;
  150.         break;
  151.     case 'D':        // don't use ISO 8859-1 encoding
  152.         useISO8859 = FALSE; 
  153.         break;
  154.     case 'f':        // body font
  155.         if (!findFont(optarg)) {
  156.         fprintf(stderr, "%s: Unknown font \"%s\".\n", prog, optarg);
  157.         usage();
  158.         }
  159.         font_name = optarg;
  160.         break;
  161.     case 'G':        // gaudy mode
  162.         gaudy = headers = TRUE;
  163.         break;
  164.     case 'm':        // multi-column output
  165.         numcol = atoi(optarg);
  166.         break;
  167.     case 'M':        // margin(s)
  168.         setupMargins(optarg);
  169.         break;
  170.     case 'o':        // outline columns
  171.         outline = inch(optarg);
  172.         break;
  173.     case 'p':        // text point size
  174.         pointSize = inch(optarg);
  175.         break;
  176.     case 'r':        // rotate page (landscape)
  177.         landscape = TRUE;
  178.         break;
  179.     case 'R':        // don't rotate page (portrait)
  180.         landscape = FALSE;
  181.         break;
  182.     case 's':        // page size
  183.         setupPageSize(optarg);
  184.         break;
  185.     case 'U':        // reverse page collation
  186.         reverse = TRUE;
  187.         break;
  188.     case 'V':        // vertical line height+spacing
  189.         lineHeight = inch(optarg);
  190.         break;
  191.     default:
  192.         fprintf(stderr,"Unrecognized option \"%c\".\n", c);
  193.         usage();
  194.     }
  195.     pageHeight = ICVT(physPageHeight);
  196.     pageWidth = ICVT(physPageWidth);
  197.  
  198.     if (reverse) {
  199.     /*
  200.      * Open the file w+ so that we can reread the temp file.
  201.      */
  202.     tempfile = tmpnam(NULL);
  203.     tf = fopen(tempfile, "w+");
  204.     if (tf == NULL)
  205.         fatal("Cannot open temporary file \"%s\"", tempfile);
  206.     unlink(tempfile);    /* remove temp file in case we get killed */
  207.     } else
  208.     tf = stdout;
  209.  
  210.     numcol = fxmax(1,numcol);
  211.     if (pointSize == -1)
  212.     pointSize = inch(numcol > 1 ? "7bp" : "10bp");
  213.     else
  214.     pointSize = fxmax(inch("3bp"), pointSize);
  215.     if (pointSize > inch("18bp"))
  216.     fprintf(stderr, "%s: Warning, point size is unusually large (>18pt).\n",
  217.         prog);
  218.     readMetrics();            // read font metrics
  219.     outline = fxmax(0L,outline);
  220.     tabWidth = tabStop * CharWidth[' '];
  221.  
  222.     if (landscape) {
  223.     Coord t = pageWidth; pageWidth = pageHeight; pageHeight = t;
  224.     }
  225.     if (lm+rm >= pageWidth || tm+bm >= pageHeight) {
  226.     fprintf(stderr,"\n?Margin values too large for page\n");
  227.     exit(1);
  228.     }
  229.  
  230.     col_width = (pageWidth - (lm + rm))/numcol;
  231.     if (numcol > 1 || outline)
  232.     col_margin = col_width/COLFRAC;
  233.     else
  234.     col_margin = 0;
  235.     /*
  236.      * TeX's baseline skip is 12pt for
  237.      * 10pt type, we preserve that ratio.
  238.      */
  239.     if (lineHeight <= 0)
  240.     lineHeight = (pointSize * 12L) / 10L;
  241.  
  242.     emitPrologue(tf, argc, argv);
  243.     if (optind < argc) {
  244.     for (; optind < argc; optind++) {
  245.         curFile = argv[optind];
  246.         FILE* fp = fopen(curFile, "r");
  247.         if (fp != NULL) {
  248.         struct stat sb;
  249.         if (fstat(fileno(fp), &sb) >= 0) {
  250.             struct tm* tm = localtime(&sb.st_mtime);
  251.             strftime(modTime, sizeof (modTime), "%X", tm);
  252.             strftime(modDate, sizeof (modDate), "%D", tm);
  253.         }
  254.         doFile(fp);
  255.         fclose(fp);
  256.         } else
  257.         fprintf(stderr,"%s: Unable to open file.\n",  argv[optind]);
  258.     }
  259.     } else {
  260.     curFile = "";
  261.     time_t t = time(0);
  262.     struct tm* tm = localtime(&t);
  263.     strftime(modTime, sizeof (modTime), "%X", tm);
  264.     strftime(modDate, sizeof (modDate), "%D", tm);
  265.     doFile(stdin);
  266.     }
  267.     if (reverse) {
  268.     /*
  269.      * Now rewind temporary file and write
  270.      * pages to stdout in reverse order.
  271.      */
  272.     rewind(tf);
  273.     Copy_Block(0L, (*pageOff)[0]-1);    /* 1st page is header stuff */
  274.     size_t last = (*pageOff)[pageOff->length()-1];
  275.     for (int k = pageNum-firstPageNum; k >= 0; --k) {
  276.         /* copy remainder in reverse order */
  277.         size_t next = (size_t) ftell(stdout);
  278.         Copy_Block((*pageOff)[k],last-1);
  279.         last = (*pageOff)[k];
  280.         (*pageOff)[k] = next;
  281.     }
  282.     if (fclose(tf))
  283.         fatal("Close failure on temporary file \"%s\"", tempfile);
  284.     }
  285.     emitTrailer(stdout);
  286.     return (0);
  287. }
  288.  
  289. static void
  290. setupPageSize(const char* name)
  291. {
  292.     PageSizeInfo* info = PageSizeInfo::getPageSizeByName(name);
  293.     if (!info)
  294.     fatal("Unknown page size \"%s\"", name);
  295.     physPageWidth = info->width() / 25.4;
  296.     physPageHeight = info->height() / 25.4;
  297.     delete info;
  298. }
  299.  
  300. /*
  301.  * Parse margin syntax: l=#,r=#,t=#,b=#
  302.  */
  303. static void
  304. setupMargins(const char* s)
  305. {
  306.     for (const char* cp = s; cp && cp[0]; cp = strchr(cp, ',')) {
  307.     if (cp[1] != '=') {
  308.         fprintf(stderr, "Bad margin syntax, expecting \"=\".\n");
  309.         usage();
  310.     }
  311.     Coord v = inch(&cp[2]);
  312.     switch (tolower(cp[0])) {
  313.     case 'b': bm = v; break;
  314.     case 'l': lm = v; break;
  315.     case 'r': rm = v; break;
  316.     case 't': tm = v; break;
  317.     default:
  318.         fprintf(stderr,"Unrecognized margin identifier \"%c\".\n", cp[0]);
  319.         usage();
  320.     }
  321.     }
  322. }
  323.  
  324. static void
  325. Copy_Block(long b1,long b2)        /* copy bytes b1..b2 to stdout */
  326. {
  327.     char buf[16*1024];
  328.  
  329.     for (long k = b1; k <= b2; k += sizeof (buf)) {
  330.     size_t cc = fxmin(sizeof (buf), (u_int) (b2-k+1));
  331.     fseek(tf, k, 0);        /* position to desired block */
  332.     if (fread(buf, 1, cc, tf) != cc)
  333.         fatal("Input block length error on temporary file [%s]", tempfile);
  334.     if (fwrite(buf, 1, cc, stdout) != cc)
  335.         fatal("Output block length error on stdout");
  336.     }
  337.     fflush(stdout);
  338. }
  339.  
  340. const char* ISOprologue1 = "\
  341. /ISOLatin1Encoding where{pop save true}{false}ifelse\n\
  342. /ISOLatin1Encoding[\n\
  343.  /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n\
  344.  /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n\
  345.  /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n\
  346.  /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n\
  347.  /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n\
  348.  /.notdef /.notdef /space /exclam /quotedbl /numbersign\n\
  349.  /dollar /percent /ampersand /quoteright /parenleft\n\
  350.  /parenright /asterisk /plus /comma /minus /period\n\
  351.  /slash /zero /one /two /three /four /five /six /seven\n\
  352.  /eight /nine /colon /semicolon /less /equal /greater\n\
  353.  /question /at /A /B /C /D /E /F /G /H /I /J /K /L /M\n\
  354.  /N /O /P /Q /R /S /T /U /V /W /X /Y /Z /bracketleft\n\
  355.  /backslash /bracketright /asciicircum /underscore\n\
  356.  /quoteleft /a /b /c /d /e /f /g /h /i /j /k /l /m\n\
  357.  /n /o /p /q /r /s /t /u /v /w /x /y /z /braceleft\n\
  358.  /bar /braceright /asciitilde /guilsinglright /fraction\n\
  359.  /florin /quotesingle /quotedblleft /guilsinglleft /fi\n\
  360.  /fl /endash /dagger /daggerdbl /bullet /quotesinglbase\n\
  361.  /quotedblbase /quotedblright /ellipsis /trademark\n\
  362.  /perthousand /grave /scaron /circumflex /Scaron /tilde\n\
  363.  /breve /zcaron /dotaccent /dotlessi /Zcaron /ring\n\
  364.  /hungarumlaut /ogonek /caron /emdash /space /exclamdown\n\
  365.  /cent /sterling /currency /yen /brokenbar /section\n\
  366.  /dieresis /copyright /ordfeminine /guillemotleft\n\
  367.  /logicalnot /hyphen /registered /macron /degree\n\
  368.  /plusminus /twosuperior /threesuperior /acute /mu\n\
  369.  /paragraph /periodcentered /cedilla /onesuperior\n\
  370.  /ordmasculine /guillemotright /onequarter /onehalf\n\
  371.  /threequarters /questiondown /Agrave /Aacute\n\
  372.  /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla\n\
  373.  /Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute\n\
  374.  /Icircumflex /Idieresis /Eth /Ntilde /Ograve /Oacute\n\
  375.  /Ocircumflex /Otilde /Odieresis /multiply /Oslash\n\
  376.  /Ugrave /Uacute /Ucircumflex /Udieresis /Yacute /Thorn\n\
  377.  /germandbls /agrave /aacute /acircumflex /atilde\n\
  378.  /adieresis /aring /ae /ccedilla /egrave /eacute\n\
  379.  /ecircumflex /edieresis /igrave /iacute /icircumflex\n\
  380.  /idieresis /eth /ntilde /ograve /oacute /ocircumflex\n\
  381.  /otilde /odieresis /divide /oslash /ugrave /uacute\n\
  382.  /ucircumflex /udieresis /yacute /thorn /ydieresis\n\
  383. ]def{restore}if\n\
  384. ";
  385. const char* ISOprologue2 = "\
  386. /reencodeISO{\n\
  387.   dup length dict begin\n\
  388.     {1 index /FID ne {def}{pop pop} ifelse} forall\n\
  389.     /Encoding ISOLatin1Encoding def\n\
  390.     currentdict\n\
  391.   end\n\
  392. }def\n\
  393. /findISO{\n\
  394.   dup /FontType known{\n\
  395.     dup /FontType get 3 ne\n\
  396.     1 index /CharStrings known{\n\
  397.       1 index /CharStrings get /Thorn known\n\
  398.     }{false}ifelse\n\
  399.     and\n\
  400.   }{false}ifelse\n\
  401. }def\n\
  402. ";
  403. const char* normalISOFont = "\
  404. /N{/%s findfont\
  405.   findISO{reencodeISO /%s-ISO exch definefont}if\
  406.   %d UP scalefont setfont\
  407. }def\n\
  408. ";
  409. const char* normalFont = "\
  410. /N{/%s findfont %d UP scalefont setfont}def\n\
  411. ";
  412.  
  413. const char* defPrologue = "\
  414. /B{gsave}def\n\
  415. /LN{show}def\n\
  416. /EL{grestore 0 -%d rmoveto}def\n\
  417. /M{0 rmoveto}def\n\
  418. /O{gsave show grestore}def\n\
  419. /LandScape{90 rotate 0 -%ld translate}def\n\
  420. /U{%d mul}def\n\
  421. /UP{U 72 div}def\n\
  422. /S{show grestore 0 -%d rmoveto}def\n\
  423. ";
  424.  
  425. const char* headerPrologue = "\
  426. /InitGaudyHeaders{\n\
  427.   /Columns exch def /HeaderY exch def /BarLength exch def\n\
  428.   /ftD /Times-Bold findfont 12 UP scalefont def\n\
  429.   /ftF /Times-Roman findfont 14 UP scalefont def\n\
  430.   /ftP /Helvetica-Bold findfont 30 UP scalefont def\n\
  431.   /fillbox{ % w h x y => -\n\
  432.     moveto 1 index 0 rlineto 0 exch rlineto neg 0 rlineto\n\
  433.     closepath fill\n\
  434.   }def\n\
  435.   /LB{ % x y w h (label) font labelColor boxColor labelPtSize => -\n\
  436.     gsave\n\
  437.     /pts exch UP def /charcolor exch def /boxcolor exch def\n\
  438.     /font exch def /label exch def\n\
  439.     /h exch def /w exch def\n\
  440.     /y exch def /x exch def\n\
  441.     boxcolor setgray w h x y fillbox\n\
  442.     /lines label length def\n\
  443.     /ly y h add h lines pts mul sub 2 div sub pts .85 mul sub def\n\
  444.     font setfont charcolor setgray\n\
  445.     label {\n\
  446.       dup stringwidth pop\n\
  447.       2 div x w 2 div add exch sub ly moveto\n\
  448.       show\n\
  449.       /ly ly pts sub def\n\
  450.     } forall\n\
  451.     grestore\n\
  452.   }def\n\
  453.   /Header{ % (file) [(date)] (page) => -\n\
  454.     /Page exch def /Date exch def /File exch def\n\
  455.     .25 U HeaderY U BarLength .1 sub U .25 U [File] ftF .97 0 14 LB\n\
  456.     .25 U HeaderY .25 add U BarLength .1 sub U .25 U [()] ftF 1 0 14 LB\n\
  457.     .25 U HeaderY U 1 U .5 U Date ftD .7 0 12 LB\n\
  458.     BarLength .75 sub U HeaderY U 1 U .5 U [Page] ftP .7 1 30 LB\n\
  459.     1 1 Columns 1 sub{\n\
  460.       BarLength Columns div mul .19 add U HeaderY U moveto 0 -10 U rlineto stroke\n\
  461.     }for\n\
  462.   }def\n\
  463. }def\n\
  464. /InitNormalHeaders{\n\
  465.   /Columns exch def /HeaderY exch def /BarLength exch def\n\
  466.   /ftF /Times-Roman findfont 14 UP scalefont def\n\
  467.   /ftP /Helvetica-Bold findfont 14 UP scalefont def\n\
  468.   /LB{ % x y w h (label) font labelColor labelPtSize => -\n\
  469.     gsave\n\
  470.     /pts exch UP def /charcolor exch def\n\
  471.     /font exch def /label exch def\n\
  472.     /h exch def /w exch def\n\
  473.     /y exch def /x exch def\n\
  474.     /ly y h add h pts sub 2 div sub pts .85 mul sub def\n\
  475.     font setfont charcolor setgray\n\
  476.     label stringwidth pop 2 div x w 2 div add exch sub ly moveto\n\
  477.     label show\n\
  478.     grestore\n\
  479.   }def\n\
  480.   /Header{ % (file) [(date)] (page) => -\n\
  481.     /Page exch def pop /File exch def\n\
  482.     .25 U HeaderY U BarLength 2 div U .5 U File ftF 0 14 LB\n\
  483.     BarLength .75 sub U HeaderY U 1 U .5 U Page ftP 0 14 LB\n\
  484.     1 1 Columns 1 sub{\n\
  485.       BarLength Columns div mul .19 add U HeaderY U moveto 0 -10 U rlineto stroke\n\
  486.     }for\n\
  487.   }def\n\
  488. }def\n\
  489. /InitNullHeaders{/Header{3{pop}repeat}def Header}def\n\
  490. ";
  491.  
  492. /*
  493.  * Emit the DSC header comments and prologue.
  494.  */
  495. static void
  496. emitPrologue(FILE* fd, int argc, char* argv[])
  497. {
  498.     fputs("%!PS-Adobe-3.0\n", fd);
  499.     fprintf(fd, "%%%%Creator: %s\n", prog);
  500.     fputs("%%Title:", fd);            /* command line */
  501.     for (; argc > 0; argc--, argv++)
  502.     fprintf(fd, " %s", argv[0]);
  503.     putc('\n', fd);
  504.     time_t t = time(0);
  505.     fprintf(fd, "%%%%CreationDate: %s", ctime(&t));
  506.     char* cp = cuserid(NULL);
  507.     fprintf(fd, "%%%%For: %s\n", cp ? cp : "");
  508.     fputs("%%Origin: 0 0\n", fd);
  509.     fprintf(fd, "%%%%BoundingBox: 0 0 %.0f %.0f\n",
  510.     physPageHeight*72, physPageWidth*72);
  511.     fputs("%%Pages: (atend)\n", fd);
  512.     fprintf(fd, "%%%%PageOrder: %s\n", reverse ? "Descend" : "Ascend");
  513.     fprintf(fd, "%%%%Orientation: %s\n", landscape ? "Landscape" : "Portrait");
  514.     fprintf(fd, "%%%%DocumentNeededResources: font %s\n", font_name);
  515.     if (gaudy) {
  516.     fputs("%%+ font Times-Bold\n", fd);
  517.     fputs("%%+ font Times-Roman\n", fd);
  518.     fputs("%%+ font Helvetica-Bold\n", fd);
  519.     }
  520.     fprintf(fd, "%%%%EndComments\n");
  521.  
  522.     fprintf(fd, "%%%%BeginProlog\n");
  523.     fputs("/$printdict 50 dict def $printdict begin\n", fd);
  524.     if (useISO8859) {
  525.     fputs(ISOprologue1, fd);
  526.     fputs(ISOprologue2, fd);
  527.     }
  528.     fprintf(fd, defPrologue, lineHeight, pageHeight, LUNIT, lineHeight);
  529.     if (useISO8859)
  530.     fprintf(tf, normalISOFont,
  531.         font_name, font_name, pointSize/20, pointSize/20); 
  532.     else
  533.     fprintf(tf, normalFont, font_name, pointSize/20);
  534.     fputs(headerPrologue, tf);
  535.     fprintf(tf, "%.2f %.2f %d Init%sHeaders\n"
  536.     , CVTI(pageWidth - (lm+rm))
  537.     , CVTI(pageHeight - tm)
  538.     , numcol
  539.     , (gaudy ? "Gaudy" : headers ? "Normal" : "Null")
  540.     );
  541.     fputs("end\n", fd);
  542.     fputs("%%EndProlog\n", fd);
  543. }
  544.  
  545. /*
  546.  * Emit the DSC trailer comments.
  547.  */
  548. static void
  549. emitTrailer(FILE* fd)
  550. {
  551.     fputs("%%Trailer\n", fd);
  552.     fprintf(fd, "%%%%Pages: %d\n", pageNum-firstPageNum);
  553.     printf("%%%%EOF\n");
  554. }
  555.  
  556. static void
  557. newPage()
  558. {
  559.     x = lm;                    // x starts at left margin
  560.     right_x = col_width - col_margin/2;        // right x, 0-relative
  561.     y = pageHeight - tm - lineHeight;        // y at page top
  562.     level = 0;                    // string paren level reset
  563.     column = 1;
  564.     boc = TRUE;
  565.     bop = TRUE;
  566. }
  567.  
  568. static void
  569. newCol()
  570. {
  571.     x += col_width;                // x, shifted
  572.     right_x += col_width;            // right x, shifted
  573.     y = pageHeight - tm - lineHeight;        // y at page top
  574.     level = 0;
  575.     column++;
  576.     boc = TRUE;
  577. }
  578.  
  579. static void
  580. beginCol()
  581. {
  582.     if (column == 1) {                // new page
  583.     if (reverse)  {
  584.         int k = pageNum-firstPageNum;
  585.         size_t off = (size_t) ftell(tf);
  586.         if (k < pageOff->length())
  587.         (*pageOff)[k] = off;
  588.         else
  589.         pageOff->append(off);
  590.     }
  591.     fprintf(tf,"%%%%Page: \"%d\" %d\n",pageNum-firstPageNum+1,pageNum);
  592.     fputs("save $printdict begin\n", tf);
  593.     fprintf(tf, ".05 dup scale N\n");
  594.     if (landscape)
  595.         fputs("LandScape\n", tf);
  596.     fprintf(tf, "(%s)[(%s)(%s)](%d)Header\n",
  597.         curFile, modDate, modTime, pageNum);
  598.     }
  599.     fprintf(tf, "%ld %ld moveto\n",x,y);
  600. }
  601.  
  602. static void
  603. beginLine()
  604. {
  605.     if (boc)
  606.     beginCol(), boc = FALSE, bop = FALSE;
  607.     fputs("B", tf);
  608. }
  609.  
  610. static void
  611. beginText()
  612. {
  613.     putc('(', tf);
  614.     level++;
  615. }
  616.  
  617. static void
  618. closeStrings(const char* cmd)
  619. {
  620.     int l = level;
  621.     for (; level > 0; level--)
  622.     putc(')', tf);
  623.     if (l > 0)
  624.     fputs(cmd, tf);
  625. }
  626.  
  627. static void
  628. doFile(FILE* fp)
  629. {
  630.     newPage();                // each file starts on a new page
  631.  
  632.     fxBool bol = TRUE;            // force line start
  633.     fxBool bot = TRUE;            // force text start
  634.     int c;
  635.     if ((c = getc(fp)) != '\f')    // discard initial form feeds
  636.     ungetc(c, fp);
  637.     Coord xoff = col_width * (column-1);
  638.     while ((c = getc(fp)) != EOF) {
  639.     switch (c) {
  640.     case '\0':            // discard nulls
  641.         break;
  642.     case '\f':            // form feed
  643.         endTextCol();
  644.         bol = bot = TRUE;
  645.         break;
  646.     case '\n':            // line break
  647.         if (bol)
  648.         beginLine();
  649.         if (bot)
  650.         beginText();
  651.         endTextLine();
  652.         bol = bot = TRUE;
  653.         xoff = col_width * (column-1);
  654.         break;
  655.     case '\r':            // check for overstriking
  656.         if ((c = getc(fp)) == '\n') {
  657.         ungetc(c,fp);        // collapse \r\n => \n
  658.         break;
  659.         }
  660.         closeStrings("O\n");    // do overstriking
  661.         bot = TRUE;            // start new string
  662.         break;
  663.     default:
  664.         Coord hm;
  665.         if (c == '\t' || c == ' ') {
  666.         /*
  667.          * Coalesce white space into one relative motion.
  668.          * The offset calculation below is to insure that
  669.          * 0 means that start of the line (no matter what
  670.          * column we're in).
  671.          */
  672.         hm = 0;
  673.         int cc = c;
  674.         Coord off = xoff - col_width*(column-1);
  675.         do {
  676.             if (cc == '\t')
  677.             hm += tabWidth - (off+hm) % tabWidth;
  678.             else
  679.             hm += CharWidth[' '];
  680.         } while ((cc = getc(fp)) == '\t' || cc == ' ');
  681.         if (cc != EOF)
  682.             ungetc(cc, fp);
  683.         /*
  684.          * If the motion is one space's worth, either
  685.          * a single blank or a tab that causes a blank's
  686.          * worth of horizontal motion, then force it
  687.          * to be treated as a blank below.
  688.          */
  689.         if (hm == CharWidth[' '])
  690.             c = ' ';
  691.         else
  692.             c = '\t';
  693.         } else
  694.         hm = CharWidth[c];
  695.         if (xoff + hm > right_x) {
  696.         if (!wrapLines)        // discard line overflow
  697.             break;
  698.         if (c == '\t')        // adjust white space motion
  699.             hm -= right_x - xoff;
  700.         endTextLine();
  701.         bol = bot = TRUE;
  702.         xoff = col_width * (column-1);
  703.         }
  704.         if (bol)
  705.         beginLine(), bol = FALSE;
  706.         if (c == '\t') {        // close open PS string and do motion
  707.         if (hm > 0) {
  708.             closeStrings("LN");
  709.             fprintf(tf, " %ld M ", hm);
  710.             bot = TRUE;        // force new string
  711.         }
  712.         } else {            // append to open PS string
  713.         if (bot)
  714.             beginText(), bot = FALSE;
  715.         if (040 <= c && c <= 0176) {
  716.             if (c == '(' || c == ')' || c == '\\')
  717.             putc('\\',tf);
  718.             putc(c,tf);
  719.         } else
  720.             fprintf(tf, "\\%03o", c);
  721.         }
  722.         xoff += hm;
  723.         break;
  724.     }
  725.     }
  726.     if (!bol)
  727.     endLine();
  728.     if (!bop) {
  729.     column = numcol;            // force page end action
  730.     endTextCol();
  731.     }
  732.     if (reverse) {
  733.     /*
  734.      * Normally, beginCol sets the pageOff entry.  Since this is
  735.      * the last output for this file, we must set it here manually.
  736.      * If more files are printed, this entry will be overwritten (with
  737.      * the same value) at the next call to beginCol.
  738.      */
  739.     size_t off = (size_t) ftell(tf);
  740.     pageOff->append(off);
  741.     }
  742. }
  743.  
  744. const char* outlineCol = "\n\
  745. gsave\
  746.     %ld setlinewidth\
  747.     newpath %ld %ld moveto\
  748.     %ld %ld rlineto\
  749.     %ld %ld rlineto\
  750.     %ld %ld rlineto\
  751.     closepath stroke \
  752. grestore\n\
  753. ";
  754.  
  755. static void
  756. endCol()
  757. {
  758.     if (outline > 0) {
  759.     fprintf(tf, outlineCol, outline,
  760.         x - col_margin, bm,        col_width, 0, 0,
  761.         pageHeight-bm-tm,        -col_width, 0);
  762.     }
  763.     if (column == numcol) {        // columns filled, start new page
  764.     pageNum++;
  765.     fputs("showpage\nend restore\n", tf);
  766.     fflush(tf);
  767.     if (ferror(tf) && errno == ENOSPC)
  768.         fatal("Output error -- disk storage probably full");
  769.     newPage();
  770.     } else
  771.     newCol();
  772. }
  773.  
  774. static void
  775. endLine()
  776. {
  777.     fputs("EL\n", tf);
  778.     if ((y -= lineHeight) < bm)
  779.     endCol();
  780. }
  781.  
  782. static void
  783. endTextCol()
  784. {
  785.     closeStrings("LN");
  786.     putc('\n', tf);
  787.     endCol();
  788. }
  789.  
  790. static void
  791. endTextLine()
  792. {
  793.     closeStrings("S\n");
  794.     if ((y -= lineHeight) < bm)
  795.     endCol();
  796. }
  797.  
  798. /*
  799.  * Convert a value of the form:
  800.  *     #.##bp        big point (1in = 72bp)
  801.  *     #.##cc        cicero (1cc = 12dd)
  802.  *     #.##cm        centimeter
  803.  *     #.##dd        didot point (1157dd = 1238pt)
  804.  *     #.##in        inch
  805.  *     #.##mm        millimeter (10mm = 1cm)
  806.  *     #.##pc        pica (1pc = 12pt)
  807.  *     #.##pt        point (72.27pt = 1in)
  808.  *     #.##sp        scaled point (65536sp = 1pt)
  809.  * to inches, returning it as the function value.  The case of
  810.  * the dimension name is ignored.  No space is permitted between
  811.  * the number and the dimension.
  812.  */
  813. static Coord
  814. inch(const char* s)
  815. {
  816.     char* cp;
  817.     double v = strtod(s, &cp);
  818.     if (cp == NULL) {
  819.     fprintf(stderr,
  820.         "%s: Unable to convert \"%s\" to a floating point number.\n",
  821.         prog, s);
  822.     exit(-1);
  823.     }
  824.     if (strncasecmp(cp,"in",2) == 0)        // inches
  825.     ;
  826.     else if (strncasecmp(cp,"cm",2) == 0)    // centimeters
  827.     v /= 2.54;
  828.     else if (strncasecmp(cp,"pt",2) == 0)    // points
  829.     v /= 72.27;
  830.     else if (strncasecmp(cp,"cc",2) == 0)    // cicero
  831.     v *= 12.0 * (1238.0 / 1157.0) / 72.27;
  832.     else if (strncasecmp(cp,"dd",2) == 0)    // didot points
  833.     v *= (1238.0 / 1157.0) / 72.27;
  834.     else if (strncasecmp(cp,"mm",2) == 0)    // millimeters
  835.     v /= 25.4;
  836.     else if (strncasecmp(cp,"pc",2) == 0)    // pica
  837.     v *= 12.0 / 72.27;
  838.     else if (strncasecmp(cp,"sp",2) == 0)    // scaled points
  839.     v /= (65536.0 * 72.27);
  840.     else                    // big points
  841.     v /= 72.0;
  842.     return ICVT(v);
  843. }
  844.  
  845. static FILE*
  846. OpenAFMFile(char* font_path)
  847. {
  848.     FILE* fd;
  849.     sprintf(font_path, "%s/%s.afm", FONTDIR, font_name);
  850.     fd = fopen(font_path, "r");
  851.     if (fd != NULL || errno != ENOENT)
  852.     return (fd);
  853.     font_path[strlen(font_path)-4] = '\0';
  854.     return (fopen(font_path, "r"));
  855. }
  856.  
  857. static fxBool
  858. getAFMLine(FILE* fp, char* buf, int bsize)
  859. {
  860.     if (fgets(buf, bsize, fp) == NULL)
  861.     return (FALSE);
  862.     char* cp = strchr(buf, '\n');
  863.     if (cp == NULL) {            // line too long, skip it
  864.     int c;
  865.     while ((c = getc(fp)) != '\n')    // skip to end of line
  866.         if (c == EOF)
  867.         return (FALSE);
  868.     cp = buf;            // force line to be skipped
  869.     }
  870.     *cp = '\0';
  871.     return (TRUE);
  872. }
  873.  
  874. static void
  875. readMetrics()
  876. {
  877.     char font_path[1024];
  878.     FILE* fp = OpenAFMFile(font_path);
  879.     if (fp == NULL) {
  880.     fprintf(stderr, "%s: can't open font metrics file %s\n",
  881.         prog,font_path);
  882.     for (int i = 0; i < NCHARS; i++)
  883.         CharWidth[i] = 600*pointSize/1000L;
  884.     return;
  885.     }
  886.     for (int i = 0; i < NCHARS; i++)
  887.     CharWidth[i] = 0;
  888.  
  889.     char buf[1024];
  890.     u_int line = 0;
  891.     do {
  892.     if (!getAFMLine(fp, buf, sizeof (buf))) {
  893.         fclose(fp);
  894.         return;            // XXX
  895.     }
  896.     line++;
  897.     } while (strncmp(buf, "StartCharMetrics", 16));
  898.     while (getAFMLine(fp, buf, sizeof (buf)) && strcmp(buf, "EndCharMetrics")) {
  899.     line++;
  900.     int ix, w;
  901.     /* read the glyph position and width */
  902.     if (sscanf(buf, "C %d ; WX %d ;", &ix, &w) == 2) {
  903.         if (ix == -1)        // end of unencoded glyphs
  904.         break;
  905.         if (ix < NCHARS)
  906.         CharWidth[ix] = w*pointSize/1000L;
  907.     } else
  908.         fprintf(stderr, "%s, line %u: format error", font_path, line);
  909.     }
  910.     fclose(fp);
  911. }
  912.  
  913. static void
  914. setDefaultFont()
  915. {
  916.     font_name = "Courier";
  917. }
  918.  
  919. static fxBool
  920. findFont(const char* name)
  921. {
  922.     fxBool ok = FALSE;
  923.     DIR* dir = opendir(FONTDIR);
  924.     if (dir) {
  925.     int len = strlen(name);
  926.     dirent* dp;
  927.     while ((dp = readdir(dir)) != NULL) {
  928.         int l = strlen(dp->d_name);        // XXX should d_namlen
  929.         if (l < len)
  930.         continue;
  931.         if (strcasecmp(name, dp->d_name) == 0) {
  932.         ok = TRUE;
  933.         break;
  934.         }
  935.         // check for .afm suffix
  936.         if (l-4 != len || strcmp(&dp->d_name[len], ".afm"))
  937.         continue;
  938.         if (strncasecmp(name, dp->d_name, len) == 0) {
  939.         ok = TRUE;
  940.         break;
  941.         }
  942.     }
  943.     closedir(dir);
  944.     }
  945.     return (ok);
  946. }
  947.  
  948. static void
  949. usage()
  950. {
  951.     fprintf(stderr, "Usage: %s"
  952.     " [-1]"
  953.     " [-2]"
  954.     " [-B]"
  955.     " [-c]"
  956.     " [-D]"
  957.     " [-f fontname]"
  958.     " [-m N]"
  959.     " [-o #]"
  960.     " [-p #]"
  961.     " [-r]"
  962.     " [-U]"
  963.     " [-Ml=#,r=#,t=#,b=#]"
  964.     " [-V #]"
  965.     " files... >out.ps\n", prog);
  966.     fprintf(stderr,"Default options:"
  967.     " -f Courier"
  968.     " -1"
  969.     " -p 11bp"
  970.     " -o 0"
  971.     "\n");
  972.     exit(1);
  973. }
  974.  
  975. static void
  976. fatal(const char* fmt ...)
  977. {
  978.     fputs("\n?", stderr);
  979.     va_list ap;
  980.     va_start(ap, fmt);
  981.     vfprintf(stderr, fmt, ap);
  982.     va_end(ap);
  983.     perror("\n?perror() says");
  984.     exit(1);
  985. }
  986.